#pragma once 
#include "../Common/Util.hpp"
#include "../Common/Log.hpp"
#include <unistd.h>
#include <sys/wait.h>
#include <sys/resource.h>

class Runner{
public:
    Runner(){}
    ~Runner(){}
public:


    //cpu_limit: 表示的是cpu使用时间的限制
    //mem_limit: 表示的是内存使用的限制(当然，我们以kb作为单位)
    static void SetProcessLimit(int cpu_limit, int mem_limit){
        //设置CPU时间
        struct rlimit cpu_rlimitl;
        cpu_rlimitl.rlim_cur = cpu_limit;
        cpu_rlimitl.rlim_max = RLIM_INFINITY; 
        setrlimit(RLIMIT_CPU, &cpu_rlimitl);

        //设置内存使用大小
        struct rlimit mem_rlimit;
        mem_rlimit.rlim_cur = mem_limit * 1024;   //转化为kb，内核中的单位是byte
        mem_rlimit.rlim_max = RLIM_INFINITY; 
        setrlimit(RLIMIT_AS, &mem_rlimit);
    }


    /*********************************
    * 这里我们返回值用int是有考量的 
    * 返回值0   代表正常
    * 返回值>0  收到信号，说明异常
    * 返回值-1  打开标准文件失败
    * 返回值-2  创建子进程失败
    * 返回值-3  当前没有可执行文件
    */

    static int Run(const std::string& name, int cpu_limit, int mem_limit){
        /************************
        * 首先，运行代码(已经编译好了)，有大概三种情况
        * 代码没问题，运行正确
        * 代码没问题，运行错误
        * 代码出错
        * 
        * 但是，在今天这个运行的环节，这个Runner只负责把代码运行好，结果、错误相关信息添加到指定文件
        * 他不关心当前代码的运行结果是否正确
        * 
        * 运行结果是否正确，这个是要取决于上层调用者进行判断的！
        * 也就是我们在做在线oj的时候，常说到的测试用例       
        */

        //获取到运行的时候应该有的相关文件路径
        std::string _exe = PathUtil::Exe(name),
        _stdin = PathUtil::Stdin(name),
        _stdout = PathUtil::Stdout(name),
        _stderr = PathUtil::Stderr(name); 

        //优化一下：如果此时，连_exe都不存在，就没办法运行，那就直接返回
        if(!FileUtil::IsFileExist(_exe)){
            LOG(ERROR) << "没有可执行文件, 无法运行" << "\n";
            return -3;
        }
        
        //把这些文件统统打开
        umask(0);
        int _stdin_fd = open(_stdin.c_str(), O_CREAT | O_RDONLY, 0664),
        _stdout_fd = open(_stdout.c_str(), O_CREAT | O_WRONLY, 0664),
        _stderr_fd = open(_stderr.c_str(), O_CREAT | O_WRONLY, 0664);
        if(_stdin_fd < 0 || _stdout_fd < 0 || _stderr_fd < 0){
            LOG(ERROR) << "内部错误: 打开std重定向文件失败" << "\n";
            return -1;
        }
        

        pid_t id = fork();
        if(id < 0){
            LOG(ERROR) << "内部错误: 创建子进程失败" << "\n";
            close(_stdin_fd);
            close(_stdout_fd);
            close(_stderr_fd);
            return -2;
        }
        else if(id == 0){
            //重定向 -> 到时候要输出的信息都是放在指定文件的
            dup2(_stdin_fd, 0);
            dup2(_stdout_fd, 1);
            dup2(_stderr_fd, 2);
            
            //这里还面临一个问题就是：万一对方提交的是恶意的代码，比如死循环，大量申请内存等；这是会对服务器造成攻击的！
            //所以，我们这里需要设置一下资源的限制 -> 用Linux接口setrlimit
            SetProcessLimit(cpu_limit, mem_limit);

            //这里就不能待环境变量了，因为这是我们自己写的程序
            execl(_exe.c_str(), _exe.c_str(), nullptr);
            //如果走到这里，就说明程序替换失败了！
            exit(1);   //直接退出
        }
        else{
            int status = 0;
            int n = waitpid(id, &status, 0);
            close(_stdin_fd);
            close(_stdout_fd);
            close(_stderr_fd);
            LOG(ERROR) << "运行结果(是否有收到信号): " << (status & 0x7F) << "\n";
            return (status & 0x7F);
        }
    }
};